// Copyright 2007-2009 Stuart Caborn, Alex Scordellis
//  
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
//  use this file except in compliance with the License. You may obtain a copy of
//  the License at
//  
//  http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
//  License for the specific language governing permissions and limitations under
//  the License.
// 


using System;
using System.Reflection;
using PostSharp.Extensibility;
using PostSharp.Laos;

namespace NSynthesis
{
    [Serializable]
    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    public class WatchAttribute : OnMethodInvocationAspect
    {
        [NonSerialized] private MethodBase compileTimeJoinPoint;

        public bool MonitorGenerics { get; set; }

        public override bool CompileTimeValidate(MethodBase method)
        {
            var validate = base.CompileTimeValidate(method);
            if (validate)
            {
                if (method.IsGenericMethodDefinition || method.IsGenericMethod || method.DeclaringType.IsGenericType) return MonitorGenerics;

//                MessageSource.MessageSink.Write(new Message(SeverityType.Info,
//                                                            "Watch",
//                                                            string.Format("CompileTimeValidate::Request bind to {0}", compileTimeJoinPoint.DeclaringType.FullName + "::" + compileTimeJoinPoint.Name),
//                                                            "NSynthesis"));
            }
            return validate;
        }

        public override void CompileTimeInitialize(MethodBase method)
        {
            compileTimeJoinPoint = method;
//            MessageSource.MessageSink.Write(new Message(SeverityType.ImportantInfo, "Watch", "CompileTimeInitialize", "NSynthesis"));
            base.CompileTimeInitialize(method);
        }

        public override OnMethodInvocationAspectOptions GetOptions()
        {
            OnMethodInvocationAspectOptions aspectOptions = compileTimeJoinPoint.IsAbstract ? OnMethodInvocationAspectOptions.WeaveSiteCall : base.GetOptions();

//            MessageSource.MessageSink.Write(new Message(SeverityType.Info,
//                                                        "Watch",
//                                                        string.Format("Binding to {0} with {1}", compileTimeJoinPoint.DeclaringType.FullName + "::" + compileTimeJoinPoint.Name, aspectOptions),
//                                                        "NSynthesis"));

            return aspectOptions;
        }

        public override void RuntimeInitialize(MethodBase theMethodForThisWatch)
        {
            base.RuntimeInitialize(theMethodForThisWatch);

            //TODO - this should be replaced by something more intelligent so that we filter as specified by the attribute!
            Synthesis.RegisterAssembly(theMethodForThisWatch.DeclaringType.Assembly);
        }

        public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        {
            try
            {
                //TODO - neater way to do this?
                if (eventArgs.Delegate.Method.DeclaringType.Assembly.FullName ==
                    "DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")
                    Synthesis.Recorder.RegisterMockMethodCall(eventArgs.Delegate.Method);
                else
                    Synthesis.Recorder.RegisterRealMethodCall(eventArgs.Delegate.Method);
            }
            catch (Exception e)
            {
                MessageSource.MessageSink.Write(new Message(SeverityType.Error, "Watch", "Error caught in watch::OnInvocation" + e, "NSynthesis"));
                throw;
            }

            try
            {
                eventArgs.Proceed();
            }
            catch (TargetInvocationException te)
            {
                throw te.InnerException;
            }
        }
    }
}